﻿using System;
using System.Collections.Generic;
using Expect.Core.BaseClasses;
using Expect.Core.Helpers;
using Expect.Core.IO;

namespace Expect.Core
{
    using System.Linq;

    using Expect.Core.Errors;
    using Expect.Core.Fixtures;
    using Expect.Core.Interfaces;

    /// <summary>
    /// Represent the definition of the fixture through a list of rows
    /// </summary>
    public class FixtureDefinition : RunnableActiveList<FixtureRow, IFixtureImplementation>
    {
        /// <summary>
        /// Constructor for fixture definition
        /// </summary>
        /// <param name="parent"></param>
        public FixtureDefinition(SpecificationDefinition parent)
        {
            Parent = parent;
            errorChildPrefix = "\t";
        }
        
        /// <summary>
        /// The definition of the cells (used in all table style fixtures)
        /// </summary>
        public FixtureRow Definition { get; set; }
        
        /// <summary>
        /// Parent specification
        /// </summary>
        public SpecificationDefinition Parent { get; set; }

        /// <summary>
        /// List of the fixture's rows
        /// </summary>
        public IList<FixtureRow> Rows
        {
            get { return ChildElements; }
        }

        private bool IsFluentFixture { get; set; }

        private FixtureRow GetRow(params string[] Cells)
        {
            FixtureRow rw = new FixtureRow();
            foreach (string s in Cells)
            {
                rw.AddCell(s);
            }
            return rw;
        }

        private FixtureRow GetRow(params object[] Cells)
        {
            FixtureRow rw = new FixtureRow();
            foreach (object ob in Cells)
            {
                rw.AddCell(ob);
            }
            return rw;
        }        

        /// <summary>
        /// Fill the ClassName of the fixture
        /// </summary>
        /// <param name="classname">The name of the fixture's implementation class</param>
        /// <returns></returns>
        public FixtureDefinition WithClass(string classname)
        {
            ClassName = classname;
            return this;
        }

        /// <summary>
        /// Fill the ClassType of the fixture
        /// </summary>
        /// <param name="t">The type of the fixture implementation</param>
        /// <returns></returns>
        public FixtureDefinition WithClass(Type t)
        {
            ClassType = t;
            return this;
        }

        internal FixtureDefinition WithRow(ParsingMetaData metaData, params string[] Cells)
        {
            FixtureRow rw = GetRow(Cells);
            rw.MetaData = metaData;
            Add(rw);
            return this;
        }

        /// <summary>
        /// Add a row with the list of cells text provided
        /// </summary>
        /// <param name="Cells">List of the cells text</param>
        /// <returns>The fixture itself (for method chaining)</returns>
        internal FixtureDefinition WithRow(params string[] Cells)
        {
            FixtureRow rw = GetRow(Cells);
            Add(rw);
            return this;
        }

        /// <summary>
        /// Add a row with the list of expected value provided
        /// </summary>
        /// <param name="Cells"></param>
        /// <returns>The fixture itself (for method chaining)</returns>
        public FixtureDefinition WithRow(params object[] Cells)
        {
            FixtureRow rw = GetRow(Cells);
            Add(rw);
            return this;
        }

        /// <summary>
        /// Add a row to the definition
        /// </summary>
        /// <param name="item"></param>
        public override void Add(FixtureRow item)
        {
            base.Add(item);

            if (Parent.Context != null)
            {
                if (ClassType != null && !HasImplementation && !Parent.Context.ResolveOnly)
                {
                    this.InitializeImplementation(Parent.Context);
                }

                if (Parent.Fixtures.Count == 1 && Rows.Count == 1 && !Parent.HasImplementation)
                {
                    Parent.Context.Resolve(Parent);
                }

                if (IsFluentFixture)
                {
                    var res = Parent.Context.AsFluentFixture(this, item);
                }
                else if (HasImplementation)
                {
                    RunRow(item);
                }

                if (this.Rows.Count == 1 && Parent.Context != null && !HasImplementation)
                {
                    Parent.Context.Resolve(this);
                    if (!HasImplementation)
                    {
                        IsFluentFixture = true;
                        RunRow(item);
                    }
                }
            }
        }

        /// <summary>
        /// Run the fixture
        /// </summary>
        public void Run()
        {
            Parent.Context.Resolve(this);

            foreach (var row in Rows)
            {
                RunRow(row);
            }
        }

        /// <summary>
        /// End fixture run. Necessary for fixtures that executes when fully defined
        /// </summary>
        public void EndRun()
        {
            if (HasImplementation)
            {
                Implementation.EndRun();
            }

            this.CalculateResult();
        }

        private void RunRow(FixtureRow row)
        {
            bool? res = null;
            if (!Parent.Context.ResolveOnly)
            {
                if (!HasClass)
                {
                    res = Parent.Context.AsFluentFixture(this, row);
                }
                else
                {
                    if (HasImplementation)
                    {
                        Implementation.Row = row;
                        res = Implementation.RunRow();
                    }
                    else
                    {
                        ElementNotFoundError.Class(ClassName, ElementKind.Class);
                    }
                }
            }

            if (res.HasValue)
            {
                if (!this.Status.Success.HasValue) this.Status.Success = res;
                if (!this.Parent.Status.Success.HasValue) this.Parent.Status.Success = res;

                Status.Success = Status.Success.Value & res;
                Parent.Status.Success = Parent.Status.Success.Value & res;
            }
        }

        /*
        /// <summary>
        /// Add a row definition with the list of cells text provided
        /// </summary>
        /// <param name="Cells">List of definition cells</param>
        /// <returns>The fixture itself (for method chaining)</returns>
        public FixtureDefinition WithDef(params string[] Cells)
        {
            Definition = GetRow(Cells);
            return this;
        }*/

        /*
        /// <summary>
        /// Add a list of arguments
        /// </summary>
        /// <param name="Cells"></param>
        /// <returns>The fixture itself (for method chaining)</returns>
        public FixtureDefinition WithArg(params string[] Cells)
        {
            Arguments = GetRow(Cells);
            return this;
        }*/

        /// <summary>
        /// Get an instance of the fixture's implementation
        /// </summary>
        /// <returns>an instance of the fixture's implementation</returns>
        public override IFixtureImplementation GetImplementation(IExpectCatalog catalog)
        {
            string[] args = new string[0];
            if (Definition != null && Definition.Cells.Count > 1 && ClassType != typeof(MethodWithFixtureDataFixture))
                args = this.Definition.ArgsForFluent().ToArray();

            var res = (IFixtureImplementation)catalog.GetInstance(ClassType, args);
            if (res == null)
            {
                Status.AddError(ElementNotFoundError.Constructor(ClassType.FullName, args.Length));
            }
            return res;
        }

        /// <summary>
        /// Get an instance of the fixture's implementation
        /// </summary>
        /// <returns>an instance of the fixture's implementation</returns>
        public override void InitializeImplementation(IExecutionContext ctx)
        {
            Implementation = GetImplementation(ctx.Catalog);
            if (Implementation != null)
            {
                Implementation.Specification = Parent;
                Implementation.Fixture = this;
            }
        }

        /// <summary>
        /// Get fixture data
        /// </summary>
        /// <returns></returns>
        public FixtureData GetFixtureData()
        {
            return this.GetFixtureData(false);
        }

        /// <summary>
        /// Get fixture data
        /// </summary>
        /// <returns></returns>
        public FixtureData GetFixtureDataWithoutFirstRow()
        {
            return this.GetFixtureData(true);
        }

        private FixtureData GetFixtureData(bool skipFirstRow)
        {
            FixtureData res = new FixtureData();
            List<string> definition = new List<string>();
            int startIdx = 0;
            if (skipFirstRow)
            {
                startIdx = 1;
            }

            for (int i = 0; i < Rows[startIdx].Cells.Count; i++)
            {
                definition.Add(Rows[startIdx].Cells[i].Text);
            }

            for (int i = startIdx + 1; i < Rows.Count; i++)
            {
                res.Add(Rows[i].GetRowData(definition));
            }

            return res;
        }

        /// <summary>
        /// Indicate if the rows starting from the provided index, has the same number of cells
        /// </summary>
        /// <param name="startRow">row index to start comparison</param>
        /// <returns></returns>
        public bool RowsHaveSameNumberOfCells(int startRow)
        {
            if (startRow < 0)
                throw new ArgumentException();

            if (startRow + 1 >= Rows.Count) return false;

            int numcells = Rows[startRow].Cells.Count;
            for (int i = startRow + 1; i < Rows.Count; i++)
            {
                if (Rows[i].Cells.Count != numcells) return false;
            }

            return true;
        }
    }

    /// <summary>
    /// Generic fixture definition. This class is used in a code context.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class FixtureDefinition<T> : FixtureDefinition where T : IFixtureImplementation
    {
        /// <summary>
        /// Constructor
        /// </summary>
        public FixtureDefinition(SpecificationDefinition parent)
            : base(parent)
        {
            ClassType = typeof(T);
            ClassName = typeof(T).Name;
        }
    }
}
